#include "pd_TCPimp.hpp"
#include "json/json.h"

static const int RECV_TOTAL_MAXSIZE = 409600;

static const int RECV_GROUP_MAXSIZE = 1400;
static const int SEND_GROUP_MAXSIZE = 450;

// pre define the return map, to make sure that: if in my cpp file, when trying to set one key:value where key not occur, just make some exception happen!

/**********************code undifined or not found***************/
static const unordered_map<string, string> CODE_INVALID = {
	{"code", "255"},
	{"info", "you should overlap it"}
};

/**********************get testcases*****************************/
static const unordered_map<string, string> GET_TESTCASES_OK = {
	{"code", "0"},
	{"input", ""},
	{"output", ""}
};

static const unordered_map<string, string> GET_TESTCASES_ERR = {
	{"code", "1"},
	{"info", ""}
};

/*********************save testcases*****************************/
static const unordered_map<string, string> SAVE_TESTCASES_OK = {
	{"code", "0"},
	{"info", ""}
};

static const unordered_map<string, string> SAVE_TESTCASES_ERR = {
	{"code", "1"},
	{"info", ""}
};

static const string CPP_PDTCP_SERVER_PUBLIC_PRE = "cpp pd tcp server:";
static const string JSON_FORMAT_ERROR_OR_MISSING_INFO = "json format error or missing info";

static const string PROBLEM_LANGUAGE_INVALID = "problem language invalid";

static const string inputTestcasesFileName = "inputSubmit.txt";
static const string outputTestcasesFileName = "outputSubmit.txt";

/************************************** MODULE FUNCTIONS ********************************/
/*
 * parse data for sending
 * para: const unordered_map<string, string>& um
 * return: string
 *
 * this function execs: unordered_map<string, string>->JSON string
 */
string parseSendData(const unordered_map<string, string>& um){
    Json::Value v;
    
    for (const auto& iterum:um) v[iterum.first] = iterum.second;
    
    Json::StreamWriterBuilder sw;
    sw.settings_["indentation"] = "";
    std::ostringstream osStream;
    string resStr;

    Json::StreamWriter* psw = sw.newStreamWriter();
    psw->write(v, &osStream);
    return osStream.str();
}

/*
 * parse data of recving
 * in default, it is an unordered_map
 * para: const string& raw, Json::Value& root
 * return pair<bool, Json::Value>: code true with none, false with error string
 *
 * note: set value of root inside the function
 */
pair<bool, string> parseRecvData(const string& raw, Json::Value& root){
    JSONCPP_STRING errs;
    Json::CharReaderBuilder readerBuilder;

    std::unique_ptr<Json::CharReader> const jsonReader(readerBuilder.newCharReader());
    bool res;
    res = jsonReader->parse(raw.c_str(), raw.c_str() + raw.length(), &root, &errs);
    if (!res || !errs.empty()) return {false, (string)errs};
    else return {true, ""};
}

/*
 * recv data, with recv() and send() inside, recv grouped data
 * para: int connfd
 * return pair<bool, string>: code true with recv content, false with error content
 */
pair<bool, string> recvData(int connfd){
    string res;
    while(true){
        char buff[RECV_GROUP_MAXSIZE + 1];
        memset(buff, 0, sizeof(char)*(RECV_GROUP_MAXSIZE + 1));
    recvData_innerstart:
        int n = recv(connfd, buff, RECV_GROUP_MAXSIZE, 0);
        if (n < 0){
            if (errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN) {    //not fatal error
                cout << "recvData group step, recv interrupt: " << strerror(errno) << "(errno: " << errno << ")" << endl;
                goto recvData_innerstart;
            }
            else{
                cout << "recvData group step, recv error: " << strerror(errno) << "(errno: " << errno << ")" << endl;
                return {false, ""};
            }
        }
        else{
            buff[n] = '\0';
            string recvGroupRecvdata = string(buff);
            if (n == RECV_GROUP_MAXSIZE){
                cout << "recvData group step, recv error: data too long!, data is:" << recvGroupRecvdata << endl;
                return {false, ""};
            }
            Json::Value rGR_v;
            pair<bool, string> rGR_ParseRet = parseRecvData(recvGroupRecvdata, rGR_v);
            if (!rGR_ParseRet.first || !rGR_v.isObject() || !rGR_v.isMember("t") || !rGR_v["t"].isString() ||
                !((rGR_v["t"].asString() == "0") || (rGR_v["t"].asString() == "1")) ||
                !rGR_v.isMember("l") || !rGR_v["l"].isString() || !rGR_v.isMember("d") || !rGR_v["d"].isString()){
                cout << "recvData group step, recv error: data format invalid, data is:" << recvGroupRecvdata << endl;
                return {false, ""};
            }
            else{
                int rGR_len;
                try{
                    rGR_len = stoi(rGR_v["l"].asString());
                }
                catch(...){
                    cout << "recvData group step, recv error: data format invalid, data is:" << recvGroupRecvdata << endl;
                    return {false, ""};
                }
                string realRecvStr = rGR_v["d"].asString();
                
                if (rGR_len != (int)realRecvStr.size()){
                    cout << "recvData group step, recv error: data format invalid, len check error, data is:" << recvGroupRecvdata << endl;
                    return {false, ""};
                }
                else{
                    res.append(realRecvStr.begin(), realRecvStr.end());
                    if (rGR_v["t"] == "1") break;
                    else if (res.size() > RECV_TOTAL_MAXSIZE){
                        cout << "recvData group step, recv error: data too long, data size is:" << recvGroupRecvdata.size() << endl;
                        return {false, ""};
                    }
                    else{
						cout << "recvData group step, recv ok: size is:" << n << endl;
                        //send 0(get group data ok) back
                        string respGroupSenddata = "{\"t\":\"0\"}";
                        if( send(connfd, respGroupSenddata.c_str(), respGroupSenddata.size(), 0) < 0){
                            cout << "recvData group step, send res data back error: " << strerror(errno) << "(errno: " << errno << ")" << endl;
                            return {false, ""};
                        }
                        continue;
                    }
                }
            }
        }
    }
	cout << endl;
    cout << "recvData: total size is:" << res.size() << endl;
    //cout << "recvData: recv msg from client:" << res << endl;
    return pair<bool, string>{true, res};
}

/*
 * parse and send data, with send() and recv() inside, send grouped data
 * para: const string& sendMsg, int connfd
 * return true if send ok else error
 */
bool sendData(const string& sendMsg, int connfd)
{
    int sMSize = sendMsg.size();
    cout << "LOG::client: send data total size:" << sMSize << endl;
    
    //拆分发送
    int sendIndex = 0;
    bool isLastGroup = false;
    while(sendIndex < sMSize){
        //gen send data1
        unordered_map<string, string> groupum;
        if ((sendIndex + SEND_GROUP_MAXSIZE) < sMSize){
            groupum["t"] = "0";
            groupum["l"] = to_string(SEND_GROUP_MAXSIZE);
            groupum["d"] = sendMsg.substr(sendIndex, SEND_GROUP_MAXSIZE);
        }
        else{
			isLastGroup = true;
            groupum["t"] = "1";
            groupum["l"] = to_string(sMSize - sendIndex);
            groupum["d"] = sendMsg.substr(sendIndex, sMSize - sendIndex);
        }
        //parse send data
        string groupStr = parseSendData(groupum);

        if( send(connfd, groupStr.c_str(), strlen(groupStr.c_str()), 0) < 0){
            cout << "sendData group step, send error: " << strerror(errno) << "(errno: " << errno << ")" << endl;
            return false;
        }
        else{
			if (isLastGroup){
				cout << "sendData group has sent all data" << endl;
				break;
			}
			
			//recv data, check it
			cout << "sendData group send one ok, size:" << " (" << groupum["l"] << "," << groupStr.size() << ") " << endl;
		
            char buff[RECV_GROUP_MAXSIZE + 1];
            memset(buff, 0, sizeof(char)*(RECV_GROUP_MAXSIZE + 1));
        sendData_innerstart:
            int n = recv(connfd, buff, RECV_GROUP_MAXSIZE, 0);
            if (n < 0){
                if (errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN) {//not fatal error
                    cout << "sendData group step, recv interrupt: " << strerror(errno) << "(errno: " << errno << ")" << endl;
                    goto sendData_innerstart;
                }
                else{
                    cout << "sendData group step, recv error: " << strerror(errno) << "(errno: " << errno << ")" << endl;
                    return false;
                }
            }
            else{
                buff[n] = '\0';
                string sendGroupRecvdata = string(buff);
                if (n == RECV_GROUP_MAXSIZE){
                    cout << "sendData group step, recv error: data too long!, data is:" << sendGroupRecvdata << endl;
                    return false;
                }
                Json::Value sGR_v;
                pair<bool, string> sGR_ParseRet = parseRecvData(sendGroupRecvdata, sGR_v);
                if (!sGR_ParseRet.first || !sGR_v.isObject() || !sGR_v.isMember("t") || !sGR_v["t"].isString() ||
                    sGR_v["t"].asString() != "0"
                    ){
                    cout << "sendData group step, recv error: data format invalid or server rep:false, data is:" << sendGroupRecvdata << endl;
                    return false;
                }
                else{
					cout << "sendData group recv one resp ok, ";
				    cout << "sendData group has some remaining data to send" << endl;
                    sendIndex += SEND_GROUP_MAXSIZE;
                    continue;
                }
            }
        }
    }
	cout << endl;
    return true;
}

/************************************ TOP LEVEL FUNCTIONS *******************************/
/*
 * merge parse data for senging and send data
 * para: const unordered_map<string, string>& um, int connfd
 * return none(no matter send ok or error, return none!)
 */
void parseAndSendData(const unordered_map<string, string>& um, int connfd){
	string sendStr = parseSendData(um);
	if (!sendData(sendStr, connfd)){
		cout << "send data failed" << endl;
	}
}

/*
 * the main worker to exec: TCP read and send
 * para: int connfd
 * return: none
 */
void workerExec(int connfd){
	//recv and parse data(json string->jSON::Value)
	pair<bool, string> recvRet = recvData(connfd);
	if (!recvRet.first){
		unordered_map<string, string> ret = CODE_INVALID;
		ret.at("info") = CPP_PDTCP_SERVER_PUBLIC_PRE + recvRet.second;
		parseAndSendData(ret, connfd);
		return;
	}
	Json::Value val;
	pair<bool, string> parseRecvRet = parseRecvData(recvRet.second, val);
	if (!parseRecvRet.first){
		unordered_map<string, string> ret = CODE_INVALID;
		ret.at("info") = CPP_PDTCP_SERVER_PUBLIC_PRE + parseRecvRet.second;
		parseAndSendData(ret, connfd);
		return;
	}
	
	//check val type and code
	if (!val.isObject() || !val.isMember("code")){
		unordered_map<string, string> ret = CODE_INVALID;
		ret.at("info") = CPP_PDTCP_SERVER_PUBLIC_PRE + JSON_FORMAT_ERROR_OR_MISSING_INFO;
		parseAndSendData(ret, connfd);
		return;
	}
	
	if (val["code"] == "1"){ //get testcases
		//check all the info values type
		if (!val.isMember("dir") || !val["dir"].isString() || !val.isMember("language") || !val["language"].isString()){
			unordered_map<string, string> ret = GET_TESTCASES_ERR;
			ret.at("info") = CPP_PDTCP_SERVER_PUBLIC_PRE + JSON_FORMAT_ERROR_OR_MISSING_INFO;
			parseAndSendData(ret, connfd);
			return;
		}
		string languageStr = val["language"].asString();
		string dirStr = val["dir"].asString();
		
		//check language
		regex languageRe("[a-zA-Z0-9_]{1,20}");
		if (!regex_match(languageStr, languageRe)){
			unordered_map<string, string> ret = GET_TESTCASES_ERR;
			ret.at("info") = CPP_PDTCP_SERVER_PUBLIC_PRE + PROBLEM_LANGUAGE_INVALID;
			parseAndSendData(ret, connfd);
			return;
		}
		
		//check and generate directory
		string blockName;
		string problemName;
		pair<bool, string> cagRet = CheckAndGenerateDirOfTestcase(dirStr, blockName, problemName, languageStr);
		if (!cagRet.first){
			unordered_map<string, string> ret = GET_TESTCASES_ERR;
			ret.at("info") = CPP_PDTCP_SERVER_PUBLIC_PRE + cagRet.second;
			parseAndSendData(ret, connfd);
			return;
		}
		
		//get files
		string inputFileFullName = blockName + "/" + problemName + "/" + languageStr + "/" + inputTestcasesFileName;
		string outputFileFullName = blockName + "/" + problemName + "/" + languageStr + "/" + outputTestcasesFileName;
		pair<bool, string> inputFileReadRet = ReadFileToString(inputFileFullName);
		pair<bool, string> outputFileReadRet = ReadFileToString(outputFileFullName);
		if (!inputFileReadRet.first || !outputFileReadRet.first){
			unordered_map<string, string> ret = GET_TESTCASES_ERR;
			ret.at("info") = CPP_PDTCP_SERVER_PUBLIC_PRE + inputFileReadRet.second + "   " + outputFileReadRet.second;
			parseAndSendData(ret, connfd);
			return;
		}
		
		//everything ok
		unordered_map<string, string> ret = GET_TESTCASES_OK;
		ret.at("input") = inputFileReadRet.second;
		ret.at("output") = outputFileReadRet.second;
		parseAndSendData(ret, connfd);
		return;
	}
	else if (val["code"] == "2"){  //save testcases
		//check all the info values type
		if (!val.isMember("dir") || !val["dir"].isString() || !val.isMember("language") || !val["language"].isString() ||
			!val.isMember("input") || !val["input"].isString() || !val.isMember("output") || !val["output"].isString()
		){
			unordered_map<string, string> ret = SAVE_TESTCASES_ERR;
			ret.at("info") = CPP_PDTCP_SERVER_PUBLIC_PRE + JSON_FORMAT_ERROR_OR_MISSING_INFO;
			parseAndSendData(ret, connfd);
			return;
		}
		string languageStr = val["language"].asString();
		string dirStr = val["dir"].asString();
		string inputStr = val["input"].asString();
		string outputStr = val["output"].asString();
		
		//check language
		regex languageRe("[a-zA-Z0-9_]{1,20}");
		if (!regex_match(languageStr, languageRe)){
			unordered_map<string, string> ret = SAVE_TESTCASES_ERR;
			ret.at("info") = CPP_PDTCP_SERVER_PUBLIC_PRE + PROBLEM_LANGUAGE_INVALID;
			parseAndSendData(ret, connfd);
			return;
		}
		
		//check and generate directory
		string blockName;
		string problemName;
		pair<bool, string> cagRet = CheckAndGenerateDirOfTestcase(dirStr, blockName, problemName, languageStr);
		if (!cagRet.first){
			unordered_map<string, string> ret = SAVE_TESTCASES_ERR;
			ret.at("info") = CPP_PDTCP_SERVER_PUBLIC_PRE + cagRet.second;
			parseAndSendData(ret, connfd);
			return;
		}
		
		//save files
		string inputFileFullName = blockName + "/" + problemName + "/" + languageStr + "/" + inputTestcasesFileName;
		string outputFileFullName = blockName + "/" + problemName + "/" + languageStr + "/" + outputTestcasesFileName;
		pair<bool, string> inputFileWriteRet = WriteStringTofile(inputFileFullName, inputStr);
		pair<bool, string> outputFileWriteRet = WriteStringTofile(outputFileFullName, outputStr);
		if (!inputFileWriteRet.first || !outputFileWriteRet.first){
			unordered_map<string, string> ret = SAVE_TESTCASES_ERR;
			ret.at("info") = CPP_PDTCP_SERVER_PUBLIC_PRE + inputFileWriteRet.second + "   " + outputFileWriteRet.second;
			parseAndSendData(ret, connfd);
			return;
		}
		
		//everything ok
		unordered_map<string, string> ret = SAVE_TESTCASES_OK;
		ret.at("info") = "save success";
		parseAndSendData(ret, connfd);
		return;
	}
	else{
		unordered_map<string, string> ret = CODE_INVALID;
		ret.at("info") = CPP_PDTCP_SERVER_PUBLIC_PRE + JSON_FORMAT_ERROR_OR_MISSING_INFO;
		parseAndSendData(ret, connfd);
		return;
	}
}
